﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using WgFrame.Net.Socket;

namespace WgFrame.Net.Peer
{
    public class HttpPeer : PeerBase
    {
        private Queue<byte[]> incomingList = new Queue<byte[]>(32);

        private List<StreamBuffer> outgoingStream;

        internal override bool Connect(string serverAddress, string appID)
        {
            if (peerConnectionState == ConnectionStateValue.Connected)
            {
                Listener.DebugReturn(DebugLevel.INFO, string.Format("Http client is allready connected: pid={0}", ConnectionId));
                return true;
            }

            ServerAddress = serverAddress;
            InitPeerBase();

            outgoingStream = new List<StreamBuffer>();
            wgSocket = new HttpSocket(this);
            if (wgSocket.Connect())
            {
                peerConnectionState = ConnectionStateValue.Connecting;
                return true;
            }
            peerConnectionState = ConnectionStateValue.Disconnected;
            return true;
        }

        internal override void Disconnect()
        {
            if (peerConnectionState != ConnectionStateValue.Disconnected && peerConnectionState != ConnectionStateValue.Disconnecting)
            {
                if (debugOut >= DebugLevel.ALL)
                {
                    base.Listener.DebugReturn(DebugLevel.ALL, "HttpPeer.Disconnect()");
                }
                StopConnection();
            }
        }

        internal override void OnConnect()
        {
            base.OnConnect();

            Dictionary<byte, object> parameters = new Dictionary<byte, object>();
            parameters.Add(1, AppId);
            parameters.Add(3, CustomInitData);
            EnqueueOperation(parameters, 0, new SendOptions { Encrypt = false }, EgMessageType.Init);
            SendOutgoingCommands();
        }

        protected override void StopConnection()
        {
            peerConnectionState = ConnectionStateValue.Disconnecting;
            wgSocket.Disconnect();
            lock (incomingList)
            {
                incomingList.Clear();
            }
            peerConnectionState = ConnectionStateValue.Disconnected;
            EnqueueStatusCallback(StatusCode.Disconnect);
        }

        internal override bool EnqueueOperation(Dictionary<byte, object> parameters, short opCode, SendOptions sendParams, EgMessageType messageType = EgMessageType.Operation)
        {
            if ((messageType == EgMessageType.Operation && peerConnectionState != ConnectionStateValue.Connected)  )
            {
                if (debugOut >= DebugLevel.ERROR)
                {
                    Listener.DebugReturn(DebugLevel.ERROR, "Cannot send op: " + opCode + "! Not connected. PeerState: " + peerConnectionState);
                }
                Listener.OnStatusChanged(StatusCode.SendError);
                return false;
            }

            StreamBuffer streamBuffer = SerializeOperationToMessage(opCode, parameters, messageType, sendParams.Encrypt);
            return EnqueueMessageAsPayload(streamBuffer);
        }

        internal override bool SendOutgoingCommands()
        {
            if (peerConnectionState == ConnectionStateValue.Disconnected)
            {
                return false;
            }

            if (timePingInterval > 0
                && peerConnectionState == ConnectionStateValue.Connected
                && Math.Abs(SupportClass.GetTickCount() - lastPingResult) > timePingInterval)
            {
                SendPing();
            }


            lock (outgoingStream)
            {
                for (int i = 0; i < outgoingStream.Count; i++)
                {
                    StreamBuffer msg = outgoingStream[i];
                    SendData(msg.GetBuffer(), msg.Length);
                    MessageBufferPool.Put(msg);
                }
                outgoingStream.Clear();
            }
            return false;
        }

        internal override void ReceiveIncomingCommands(byte[] inBuff, int len)
        {
            if (inBuff.Length == 0)
            {
                return;
            }

            timestampOfLastReceive = SupportClass.GetTickCount();

            using (BinaryReader reader = new BinaryReader(new MemoryStream(inBuff)))
            {
                int num = reader.ReadInt32();
                if (inBuff.Length - 4 < num * 4)
                {
                    return;
                }

                for (int i = 0; i < num; i++)
                {
                    int count = reader.ReadInt32();
                    byte[] buffer = reader.ReadBytes(count);

                    byte[] inBufferCopy = new byte[buffer.Length];
                    Buffer.BlockCopy(buffer, 0, inBufferCopy, 0, buffer.Length);
                    lock (incomingList)
                    {
                        incomingList.Enqueue(inBufferCopy);
                    }
                }
            }
        }

        internal override bool DispatchIncomingCommands()
        {
            if (peerConnectionState == ConnectionStateValue.Connected
                && DisconnectTimeout > 0
                && SupportClass.GetTickCount() - timestampOfLastReceive > DisconnectTimeout)
            {
                EnqueueStatusCallback(StatusCode.TimeoutDisconnect);
                EnqueueActionForDispatch(Disconnect);
            }

            while (true)
            {
                MyAction action;
                lock (ActionQueue)
                {
                    if (ActionQueue.Count <= 0)
                    {
                        break;
                    }
                    action = ActionQueue.Dequeue();
                    action();
                }
            }

            byte[] payload;
            lock (incomingList)
            {
                if (incomingList.Count <= 0)
                {
                    return false;
                }
                payload = incomingList.Dequeue();
            }
            return DeserializeMessageAndCallback(new StreamBuffer(payload));
        }

        protected override void FetchServerTimestamp()
        {
            if (peerConnectionState != ConnectionStateValue.Connected)
            {
                if ((int)debugOut >= 3)
                {
                    Listener.DebugReturn(DebugLevel.INFO, "FetchServerTimestamp() was skipped, as the client is not connected. Current ConnectionState: " + peerConnectionState);
                }
                Listener.OnStatusChanged(StatusCode.SendError);
            }
            else
            {
                SendPing();
                serverTimeOffsetIsAvailable = false;
            }
        }

        private bool EnqueueMessageAsPayload(StreamBuffer opMessage)
        {
            if (opMessage == null)
            {
                return false;
            }
            lock (outgoingStream)
            {
                outgoingStream.Add(opMessage);
            }
            return true;
        }
    }
}
